feat(forecast_nwp): expose member= ensemble selector for GEFS/CFS (#74)#75
Merged
Merged
Conversation
- Add keyword-only member: str | None = None to the weather impl - Validate member EARLY (pre-[nwp]-import): reject on non-member models naming gefs/cfs; reject out-of-enum values listing sorted valid members - Thread member to build_fetch_plan via per_model_kwargs only when non-None (member=None stays byte-identical — never overrides the path-builder default) - Multi-cycle recursion forwards member to each single-cycle call - TDD: TestForecastNwpMember (A-F) written RED first, then GREEN Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ity (#74) - Core mostlyright.forecasts.forecast_nwp accepts member= and forwards it verbatim to the weather impl (validation stays in the weather package) - TS ForecastNwpOptions exposes optional member?: string (signature-forward parity per the Dual-SDK rule; runtime stub still throws) - TDD: core wrapper test written RED (TypeError) then GREEN; TS test locks the member? option shape at compile time Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…count (#74) - GEFS row: 31-member ensemble (c00 + p01..p30) plus avg/spr statistical products — 33 values (was incorrectly '32 members') - CFS row: document member= (01..04, default 01) - Add an 'Ensemble members (member=)' section with GEFS/CFS examples, validation behavior, and the no-member-column scope note Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… architect iter-1 HIGH) The mirror-loop call site passing member= to _try_fetch_records_for_mirror had zero test executions (CI lacks the [nwp] extra; Tests C/E hit the helper directly; Test F intercepts the recursion). Stub the lazy [nwp] imports into sys.modules and capture the helper kwargs through the public forecast_nwp() entry point. Mutation-verified: deleting member=member at the call site fails this test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
✅ Docs-required check: PASS API-surface change includes docs updates — no reminder needed. API-surface files changed: Docs files changed: |
|
Parity ticket gate: PASSED See |
…r floor to 1.7.0 (#74) Core 1.7.0 passing member=member unconditionally would TypeError on EVERY forecast_nwp call against a pre-member mostlyrightmd-weather (<=1.6.0) installed outside the [research] extra. Thread member only when set (default calls stay skew-tolerant), pin the [research] extra to weather>=1.7.0 (mirrors #64 codex P1 precedent for variables=), and pin the default-call omission with a core-layer test mirroring weather Test D. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the docs/API mismatch from #74:
docs/forecasts.mddocumented amember=kwarg for GEFS ensemble selection, butforecast_nwp()never exposed it — the plumbing (_gefs_path(member=...),_cfs_path(member=...),build_fetch_plan(**per_model_kwargs)) supported member selection all along. This PR wires the public surface to it.(Referenced issue: #74 — closing keyword lands in the release PR to
main.)What changed
mostlyright.weather.forecast_nwp.forecast_nwp— new keyword-onlymember: str | None = None. Validated early (before the lazy[nwp]imports, after the reserved/MSC/legacy gates) against_MEMBER_CAPABLE_MODELS = {"gefs", "cfs"}and the closedGEFS_MEMBERS/CFS_MEMBERSenums:member=on any other model →ValueErrornaming the member-capable models.ValueErrorlisting the sorted valid members.build_fetch_planonly when non-None —member=None(default) is byte-identical to pre-change behavior (path-builder defaultsc00/01apply). Both the single-cycle mirror loop and the multi-cycle_fetch_cyclerecursion carry it.mostlyright.forecasts.forecast_nwp(core wrapper) — accepts + forwardsmember(validation stays in the weather impl, single source of truth).docs/forecasts.md— GEFS/CFS rows + new "Ensemble members" section; corrects the GEFS member-count wording (31-member ensemblec00+p01..p30, plusavg/sprstatistical products = 33 enum values, previously "32 members").membercolumn (selector only; noted in docs as future work)._nwp_archive.py/_nwp_grids/(already member-capable). RRFS declares members but its path builder isn't member-wired → intentionally excluded.TS Parity
Same-PR parity per CROSS-SDK-SYNC Recipe A (no parity ticket needed):
ForecastNwpOptionsgainsreadonly member?: stringwith a doc comment mirroring the Python semantics. TS NWP remains the v2.0+ runtime-throw stub — signature-forward only, zero runtime bytes (interface + TSDoc erased by tsup). Compile-level test locks the options shape;exactOptionalPropertyTypessemantics verified (omission ≡ PythonNone).Tests (TDD — RED confirmed before GREEN)
TestForecastNwpMember(8 tests, weather): non-member model rejection; invalid-member rejection listing the real enum; helper-level threading for gefs (p05) + cfs (03) via kwargs capture through the realbuild_fetch_plan+ 404MockTransport;member=Noneomits the kwarg (regression pin); pre-import ordering (valid member without[nwp]extra →SourceUnavailableError, notValueError); multi-cycle recursion carries member; public single-cycle threading (lazy-import gate stubbed viasys.modules, helper kwargs captured, mutation-verified — deletingmember=memberat the call site fails it).memberverbatim (Mock on the delegation target).NwpNotAvailableError.Review record (two-reviewer loop per REVIEW-DISCIPLINE.md, mixed-language → 3 reviewers)
The iter-1 HIGH: the public single-cycle
member=membercall site had zero test executions in any environment (CI lacks[nwp]; helper tests bypass the public fn; the multi-cycle test intercepts the recursion) — deleting it would silently fetchc00while the suite stayed green. Fixed in569c894with a CI-safe public-path test; iter-2 architect independently reproduced the mutation kill and audited thepatch.dict(sys.modules)restore semantics in both with/without-extra environments.Verification
uv run pytest -m "not live" -q— full suite green (incl. parity gate, untouched)uv run ruff check .+ruff format --check .— cleanpnpm -r run typecheck+pnpm -r run build— green🤖 Generated with Claude Code
Amendment (post-loop): cross-version skew guard
0753d9e— found while preparing the release: the core wrapper passedmember=memberunconditionally, so core 1.7.0 + a pre-membermostlyrightmd-weather<=1.6.0(reachable outside the[research]extra) wouldTypeErroron everyforecast_nwpcall. Fixed: member threads only when set (explicitmember=None≡ omission);[research]extra floor bumped tomostlyrightmd-weather>=1.7.0,<2.0(mirrors the #64variables=precedent, cff13b8); new core test pins the default-call omission. Amendment review: Codex + Python Architect re-dispatched (floor changes are never-skip) — Python Architect PASS (independently mutation-verified both directions; confirmed no other floor assertions;uv lock --checkunaffected), Codex PASS.